<?php
/**
 * [ActiveRecord] 模型数据操作或查询条件类,构造出查询SQL
 * 
 * @version $Id: Condition.php 357 2012-07-23 07:26:19Z linsir123 $
 * @package Mysql
 */
class ArCondition
{
	/**
	 * 数据表替换
	 */
	const AS_TABLE = '`a`';
	
	/**
	 * @var 数据表别名
	 */
	public $asTable;
	
	/**
	 * @var 条件数组
	 */
	public $data;
	
	/**
	 * @var `数据库表名`
	 */
	public $table;
	
	/**
	 * 构造函数
	 * 
	 * @param string $table 需要查询的数据库表名
	 */
	public function __construct($table)
	{
		$this->table = $table;
		
		///
		$this->clean();
	}
	
	/**
	 * 输出对象对应的SQL语句
	 */
	public function __toString()
	{
		if (empty($this->data)) 
			return '';
			
		///
		$list = array();
		foreach ($this->data as $v) {
			$func = $v['type']==1 ? '_fromArray' : '_fromString';
			$params = array(
				'data'=>$v['data'],
				'and'=>$v['and'],
				'as_table'=>$v['as_table']
			);
			$tmp = call_user_func_array(array($this, $func), $params);
			if ( ! empty($tmp)) 
				$list[] = $tmp;
		}
		
		return ' WHERE 1'.implode(' ', $list);
	}
	
	/**
	 * 添加条件
	 * 
	 * @param mixed $data 条件值数组(若采用数组方式则是一个二维数组)
	 * @param bool $and 条件连接关系(true:`AND`, false:`OR`)
	 */
	public function add($data, $and = true)
	{
		if (count($data) == 1 && is_array($data[0])) {
			$data = array_shift($data);
			$type = 1;
		} else 
			$type = 0;
			
		///
		$this->data[] = array('type'=>$type, 'data'=>$data, 'and'=>$and, 'as_table'=>null);
	}
	
	/**
	 * 清空条件内容
	 */
	public function clean()
	{
		$this->data = array();
		$this->asTable = null;
	}
	
	/**
	 * 重新设置操作的数据表名
	 * 
	 * @param string $table 需要查询的数据库表名
	 */
	public function setTable($table)
	{
		$this->table = $table;
	}
	
	/**
	 * 表连接时,将数据进行合并
	 * 
	 * @param ArCondition $oCodition 被连接的对象
	 * @param string $asTable AS表名
	 */
	public function join($oCodition, $asTable)
	{
		foreach ($oCodition->data as $v) {
			if (empty($v['as_table'])) 
				$v['as_table'] = $asTable;
				
			///
			$this->data[] = $v;
		}
	}
	
	
	/// 以下为私有方法 ///
	
	
	/**
	 * 取得默认的数据表别名
	 */
	private function _getAsTable($default = null)
	{
		if ( ! empty($default)) 
			return $default;
			
		///
		return empty($this->asTable) ? self::AS_TABLE : $this->asTable;
	}
	
	/**
	 * 字符串条件,解析方法
	 * // 使用(?)来替换
	 * 
	 * @example where('id=1')
	 * @example where('id', 1)
	 * @example where('id=?', 1)
	 * @example where('id in(?)', '1,2,3')
	 * @example where('title like "%?%"', 'linxs')
	 */
	private function _fromString($data, $and, $asTable = null)
	{
		$asTable = $this->_getAsTable($asTable);
		
		///
		$pIn = "/IN\s?\($/i";
		$pLike = "/LIKE [\'|\"](\%)?$/i";
		$format = explode('?', $data[0]);
		$sql = '';
		$i = 0;
		
		///
		if (count($format) == 1 && count($data) == 2) {
			$sql = $this->_formatSql($data[0], $data[1], $asTable);
			return $this->_conn($and, $sql);
		}
		
		///
		array_shift($data);
		if (count($format)-1 == count($data)) {
			foreach ($format as $tmp) {
				if (isset($data[$i])) {
					$var = $data[$i];
					if (preg_match($pLike, $tmp) || preg_match($pIn, $tmp)) 
						$tmp .= $var;
					else
						$tmp .= lpString::sqlEscape($var);
				}
				
				///
				$sql .= $tmp;
				$i++;
			}
			if ( ! empty($sql)) 
				return $this->_conn($and, $sql);
		}
		
		return ;
	}
	
	/**
	 * 数组条件,解析方法
	 * 
	 * @example where(array('id'=>2,'uid'=>1))
	 * @example where(array('id'=>array('in', array(1,2,3))))
	 * @example where(array('title'=>array('like', '"%keyword%"')))
	 * @example where(array('title'=>array('like', '"%?%"', $keyword)))
	 */
	private function _fromArray($data, $and, $asTable = null)
	{
		$asTable = $this->_getAsTable($asTable);
		
		///
		$i = 0;
		$sql = '';
		$count = count($data);
		foreach ($data as $field => $var) {
			if ($i > 0 && $i < $count) 
				$sql .= ' AND ';
				
			///
			$i++;
			$sql .= $this->_formatSql($field, $var, $asTable);
		}
		
		///
		if ( ! empty($sql)) 
			return $this->_conn($and, $sql);
	}
	
	/**
	 * 格式化
	 * // 格式：key => val
	 * 
	 * @example where('id', 2)
	 * @example where('id', array('in', array(1,2,3)))
	 * @example where('title', array('like', '"%keyword%"'))
	 * @example where('title', array('like', '"%?%"', $keyword))
	 */
	private function _formatSql($field, $var, $asTable)
	{
		$sql = '';
		
		///
		$field = $asTable.'.'.lpString::dbQuote($field);
		
		///
		if (is_array($var) && count($var) >= 2) {
			$type = array_shift($var);
			$type = strtoupper($type);
			switch ($type) {
				case 'IN':
				case 'NOT IN':
					if (is_array($var[0]) && ! empty($var[0])) {
						$data = lpString::sqlEscape($var[0]);
						$data = implode(',', $data);
						
						///
						$sql .= $field." {$type}(".$data.')';
					}
					break;
					
				case 'BETWEEN':
				case 'NOT BETWEEN':
					if (count($var) == 2) {
						$start = lpString::sqlEscape($var[0]);
						$end = lpString::sqlEscape($var[1]);
						
						///
						$sql .= $field." {$type} ".$start." AND ".$end;
					}
					break;
					
				case 'LIKE':
				case 'NOT LIKE':
					if (count($var) == 2) {
						$keyword = addcslashes($var[1], '%_"\'');
						$data = str_replace('?', $keyword, $var[0]);
					} else 
						$data = lpString::sqlEscape($var[0]);
						
					///
					$sql .= $field." {$type} ".$data;
					break;
					
				default:
					$data = lpString::sqlEscape($var[0]);
					
					///
					$sql .= $field." {$type} ".$data;
					break;
			}
		} else 
			$sql .= $field.' = '.lpString::sqlEscape($var);
			
		return $sql;
	}
	
	/**
	 * 连接SQL
	 */
	private function _conn($and, $sql)
	{
		if ($and===true) 
			return " AND {$sql}";
			
		return " OR ({$sql})";
	}
}
